﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks.Dataflow;
using System.Timers;
using NLog;
using SmartQuant;

namespace QuantBox
{
    public enum SelfTradePreventMethod : byte
    {
        Reject,
        WaitUntilDone
    }

    public class OrderRegister
    {
        private class ReminderData : DataObject
        {
            public override byte TypeId => DataObjectType.Reminder;
        }

        private class MyOrderManager
        {
            private readonly Dictionary<int, Order> _orders;
            private int _orderIndex;

            public MyOrderManager()
            {
                _orders = new Dictionary<int, Order>(1000);
            }

            public void Add(Order order)
            {
                order.SetOrderIndex(++_orderIndex);
                _orders[_orderIndex] = order;
            }

            public void Remove(Order order)
            {
                _orders.Remove(order.GetOrderIndex());
            }

            public Order? Get(int index)
            {
                return _orders.TryGetValue(index, out var order) ? order : null;
            }
        }

        private class PriceBook
        {
            private readonly MyOrderManager _orderManager;
            private readonly List<long> _sellPriceList;
            private readonly List<long> _buyPriceList;

            public PriceBook(MyOrderManager orderManager)
            {
                _orderManager = orderManager;
                _sellPriceList = new List<long>();
                _buyPriceList = new List<long>();
            }

            public void AddOrder(Order order)
            {
                _orderManager.Add(order);
                var value = GetOrderValue(order);
                if (order.Side == OrderSide.Buy)
                {
                    _buyPriceList.Add(value);
                    _buyPriceList.Sort();
                }
                else
                {
                    _sellPriceList.Add(value);
                    _sellPriceList.Sort();
                }
            }

            private static long GetOrderValue(Order order)
            {
                var price = (int)(order.Price / order.Instrument.TickSize);
                if (order.Type == OrderType.Market)
                {
                    price = order.Side == OrderSide.Buy ? int.MaxValue : int.MinValue;
                }
                var value = new Skyline.MakeLong(price, order.GetOrderIndex()).Value;
                return value;
            }

            public void RemoveOrder(Order order)
            {
                var value = GetOrderValue(order);
                if (order.Side == OrderSide.Buy)
                {
                    _buyPriceList.Remove(value);
                }
                else
                {
                    _sellPriceList.Remove(value);
                }
                _orderManager.Remove(order);
            }

            private Order? GetOrder(long value)
            {
                return _orderManager.Get(new Skyline.MakeLong(value).Low);
            }

            public Order? TradeTest(Order order)
            {
                if (order.Type == OrderType.Market)
                {
                    if (order.Side == OrderSide.Buy)
                    {
                        return _sellPriceList.Count > 0 ? GetOrder(_sellPriceList.First()) : null;
                    }

                    return _buyPriceList.Count > 0 ? GetOrder(_buyPriceList.Last()) : null;
                }
                var price = (int)(order.Price / order.Instrument.TickSize);

                if (order.Side == OrderSide.Buy)
                {
                    if (_sellPriceList.Count == 0)
                    {
                        return null;
                    }

                    var value = _sellPriceList.First();
                    return price >= new Skyline.MakeLong(value).High ? GetOrder(value) : null;
                }
                else
                {
                    if (_buyPriceList.Count == 0)
                    {
                        return null;
                    }

                    var value = _buyPriceList.Last();
                    return price <= new Skyline.MakeLong(value).High ? GetOrder(value) : null;
                }
            }
        }

        public static readonly OrderRegister Instance;
        private readonly ActionBlock<DataObject> _actionBlock;
        private readonly Dictionary<Framework, EventQueue> _queues;
        private readonly Dictionary<string, PriceBook> _priceBookList;
        private readonly MyOrderManager _orderManager;
        private readonly ReminderData _reminder;
        private readonly Timer _timer;
        private readonly List<Framework> _frameworks;
        private List<Order> _pendingList;
        private Logger? _logger;

        public SelfTradePreventMethod PreventMethod { get; set; } = SelfTradePreventMethod.WaitUntilDone;
        public TimeSpan WaitTimeout { get; set; } = TimeSpan.Zero;

        static OrderRegister()
        {
            Instance = new OrderRegister();
        }

        private EventQueue GetEventQueue(Framework framework)
        {
            if (!_queues.TryGetValue(framework, out var queue))
            {
                queue = new EventQueue(size: 10, bus: framework.EventBus);
                queue.Enqueue(new OnQueueOpened(queue));
                framework.EventBus.ExecutionPipe.Add(queue);
                _queues.Add(framework, queue);
            }
            return queue;
        }

        private void SendOrderCancel(Order order)
        {
            var framework = order.Portfolio.GetFramework();
            GetEventQueue(framework).Enqueue(CreateCancelReport(order));
        }

        private void SendOrderReject(Order order)
        {
            var framework = order.Portfolio.GetFramework();
            GetEventQueue(framework).Enqueue(CreateRejectReport(order));
        }

        private static ExecutionReport CreateCancelReport(Order order)
        {
            var report = new ExecutionReport(order);
            report.OrdStatus = OrderStatus.Cancelled;
            report.Text = "等待超时";
            report.ExecType = ExecType.ExecCancelled;
            return report;
        }

        private static ExecutionReport CreateRejectReport(Order order)
        {
            var report = new ExecutionReport(order);
            report.OrdStatus = OrderStatus.Rejected;
            report.Text = "防止自成交";
            report.ExecType = ExecType.ExecRejected;
            return report;
        }

        private void CheckSelfTrade(Order order)
        {
            if (!_priceBookList.TryGetValue(order.Instrument.Symbol, out var book))
            {
                book = new PriceBook(_orderManager);
                _priceBookList.Add(order.Instrument.Symbol, book);
            }

            var match = book.TradeTest(order);
            if (match != null)
            {
                var message = $"{order.Instrument.Symbol}, order:{order.ClOrderId},{order.Text},{order.Price}, match:{match.ClOrderId},{match.Text},{match.Price}";
                if (PreventMethod == SelfTradePreventMethod.Reject)
                {
                    _logger!.Debug($"检测到自成交可能进行撤单, {message}");
                    SendOrderReject(order);
                }
                else
                {
                    _logger!.Debug($"检测到自成交可能进入等待队列, {message}");
                    order.DateTime = DateTime.Now;
                    _pendingList.Add(order);
                }
            }
            else
            {
                book.AddOrder(order);
                order.Send();
            }
        }

        private void Process(DataObject data)
        {
            switch (data.TypeId)
            {
                case DataObjectType.Order:
                    CheckSelfTrade((Order)data);
                    break;
                case DataObjectType.ExecutionReport:
                    ProcessReport((ExecutionReport)data);
                    break;
                case DataObjectType.Reminder:
                    if (PreventMethod == SelfTradePreventMethod.WaitUntilDone)
                    {
                        ProcessTimeout();
                    }
                    break;
            }
        }

        private void ProcessTimeout()
        {
            if (_pendingList.Count == 0 || WaitTimeout == TimeSpan.Zero) { return; }
            var temp = new List<Order>();
            foreach (var item in _pendingList)
            {
                if (DateTime.Now - item.DateTime > WaitTimeout)
                {
                    SendOrderCancel(item);
                    continue;
                }
                temp.Add(item);
            }
            _pendingList = temp;
        }

        private void ProcessReport(ExecutionReport report)
        {
            switch (report.OrdStatus)
            {
                case OrderStatus.Rejected:
                case OrderStatus.Filled:
                case OrderStatus.Cancelled:
                case OrderStatus.Expired:
                case OrderStatus.Replaced:
                    break;
                default:
                    return;
            }

            var order = report.Order;
            if (!_priceBookList.TryGetValue(order.Instrument.Symbol, out var book))
                return;

            book.RemoveOrder(order);
            _logger!.Debug($"{order.ClOrderId},{order.Text},{report.ExecType},{report.OrdStatus}");

            if (_pendingList.Count == 0)
            {
                return;
            }

            var temp = new List<Order>();
            foreach (var item in _pendingList)
            {
                if (order.Side != item.Side)
                {
                    if (book.TradeTest(item) == null)
                    {
                        var message = $"{order.Instrument.Symbol}, order:{order.ClOrderId},{order.Text},{order.Price}, match:{order.ClOrderId},{order.Text},{order.Price}";
                        _logger!.Debug($"交易对手已完成发生订单, {message}");
                        book.AddOrder(item);
                        item.Send();
                        continue;
                    }
                }
                temp.Add(item);
            }
            _pendingList = temp;
        }

        private void TimerOnElapsed(object sender, ElapsedEventArgs e)
        {
            Post(_reminder);
        }

        public OrderRegister()
        {
            _reminder = new ReminderData();
            _queues = new Dictionary<Framework, EventQueue>();
            _actionBlock = new ActionBlock<DataObject>(Process);
            _priceBookList = new Dictionary<string, PriceBook>();
            _orderManager = new MyOrderManager();
            _pendingList = new List<Order>();
            _frameworks = new List<Framework>();
            _timer = new Timer(1000);
            _timer.Elapsed += TimerOnElapsed;
            _timer.Enabled = true;
        }

        public void Init(params Framework[] frameworks)
        {
            _logger ??= LogManager.GetLogger("order_register");
            _frameworks.AddRange(frameworks);
            foreach (var item in frameworks)
            {
                item.EventManager.Dispatcher.ExecutionReport += OnExecutionReport;
            }
        }

        private void OnExecutionReport(object sender, ExecutionReport report)
        {
            Post(report);
        }

        public void Post(DataObject data)
        {
            _actionBlock.Post(data);
        }

        public void Close()
        {
            foreach (var framework in _frameworks)
            {
                framework.EventManager.Dispatcher.ExecutionReport -= OnExecutionReport;
            }
            _timer.Enabled = false;
            _actionBlock.Complete();
            _actionBlock.Completion.Wait();
            _priceBookList.Clear();
            _pendingList.Clear();
        }
    }
}
